/*-------------------------------------------------------------------------
 *
 * md.c
 *	  此代码管理驻留在磁盘上的关系。
 *
 * 或者至少，这就是伯克利人命名
 * 这个文件时的想法。实际上，这段代码提供的是一个接口，
 * 从 smgr API 到类 Unix 文件系统 API，使其可以与操作系统提供文件系统支持的任何设备配合使用。
 * 不管位元是在旋转的铁锈上还是其他存储技术上，这都无所谓。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/storage/smgr/md.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include <unistd.h>
#include <fcntl.h>
#include <sys/file.h>

#include "access/xlog.h"
#include "access/xlogutils.h"
#include "commands/tablespace.h"
#include "miscadmin.h"
#include "pg_trace.h"
#include "pgstat.h"
#include "postmaster/bgwriter.h"
#include "storage/bufmgr.h"
#include "storage/fd.h"
#include "storage/md.h"
#include "storage/relfilenode.h"
#include "storage/smgr.h"
#include "storage/sync.h"
#include "utils/hsearch.h"
#include "utils/memutils.h"
#ifdef FDD
#include "utils/fdb_page_encrypt.h"
#define MIN_USER_TABLE_OID 16384 //来自 include/access/transam.h
#endif //FDD

/*
 *	磁盘存储管理器在其自己的描述符池中跟踪打开的文件
 *	描述符。这是为了支持大于操作系统文件大小限制（通常是2GB）的关系。
 *	为了做到这一点，我们将关系分解为每个小于OS文件大小限制的“段”
 *	文件。段大小由pg_config.h中的RELSEG_SIZE配置常量设置。
 *
 *	在磁盘上，一个关系必须由连续编号的段文件组成，模式为
 *		-- 零个或多个完全段，每个完全段正好包含RELSEG_SIZE个块
 *		-- 正好一个部分段，大小为0 <= size < RELSEG_SIZE个块
 *		-- 可选地，任何数量的大小为0块的非活跃段。
 *	完全段和部分段统称为“活跃段”。
 *	非活跃段是曾经包含数据但目前由于mdtruncate()操作不再需要的段。
 *	之所以将它们保留为大小为零，而不是解除链接，是因为其他
 *	后端和/或检查点可能持有对这些段的打开文件引用。
 *	如果在mdtruncate()之后关系再次扩展，使得一个被停用的段
 *	再次变为活跃段，确保这些文件引用仍然有效是重要的——
 *	否则数据可能会写入一个将最终消失的未链接旧段文件。
 *
 *	文件描述符存储在SMgrRelation中的每个fork的md_seg_fds数组中。
 *	这些数组的长度存储在md_num_open_segs中。
 *	注意，fork的md_num_open_segs具有特定值并不一定意味着
 *	关系没有其他段；我们可能只是没有打开下一个段。（反正我们无法
 *	确保“所有段都在数组中”这个不变量，因为另一个后端可能在我们
 *	没有查看时扩展关系。）然而，我们没有非活跃段的条目；
 *	一旦我们找到一个部分段，就假设任何后续段都是非活跃的。
 *
 *	整个MdfdVec数组在MdCxt内存上下文中进行palloc。
 */

typedef struct _MdfdVec
{
	File		mdfd_vfd;		/* fd.c池中的fd号 */
	BlockNumber mdfd_segno;		/* 从0开始的段号 */
} MdfdVec;

static MemoryContext MdCxt;		/* 所有MdfdVec对象的上下文 */


/* 填充描述md.c段文件的文件标签。 */
#define INIT_MD_FILETAG(a,xx_rnode,xx_forknum,xx_segno) \
( \
	memset(&(a), 0, sizeof(FileTag)), \
	(a).handler = SYNC_HANDLER_MD, \
	(a).rnode = (xx_rnode), \
	(a).forknum = (xx_forknum), \
	(a).segno = (xx_segno) \
)


/*** mdopen和_mdfd_getseg的行为 ***/
/* 如果段不存在则报告错误 */
#define EXTENSION_FAIL				(1 << 0)
/* 如果段不存在则返回NULL */
#define EXTENSION_RETURN_NULL		(1 << 1)
/* 根据需要创建新段 */
#define EXTENSION_CREATE			(1 << 2)
/* 在恢复期间根据需要创建新段 */
#define EXTENSION_CREATE_RECOVERY	(1 << 3)

/*
 * 允许打开之前由小于 RELSEG_SIZE 的段（例如，非活动段）前导的段。注意，这会破坏 mdnblocks() 和相关功能，从此以后 - 目前这是可以的，因为这只在检查点中是必要的，而检查点从不使用 mdnblocks()。
 */
#define EXTENSION_DONT_CHECK_SIZE	(1 << 4)
/* 如果未打开，则不要尝试打开段 */
#define EXTENSION_DONT_OPEN			(1 << 5)


/* 本地例程 */
static void fc_mdunlinkfork(RelFileNodeBackend fc_rnode, ForkNumber fc_forkNum,
						 bool fc_isRedo);
static MdfdVec *fc_mdopenfork(SMgrRelation fc_reln, ForkNumber fc_forknum, int fc_behavior);
static void fc_register_dirty_segment(SMgrRelation fc_reln, ForkNumber fc_forknum,
								   MdfdVec *fc_seg);
static void fc_register_unlink_segment(RelFileNodeBackend fc_rnode, ForkNumber fc_forknum,
									BlockNumber fc_segno);
static void fc_register_forget_request(RelFileNodeBackend fc_rnode, ForkNumber fc_forknum,
									BlockNumber fc_segno);
static void fc__fdvec_resize(SMgrRelation fc_reln,
						  ForkNumber fc_forknum,
						  int fc_nseg);
static char *fc__mdfd_segpath(SMgrRelation fc_reln, ForkNumber fc_forknum,
						   BlockNumber fc_segno);
static MdfdVec *fc__mdfd_openseg(SMgrRelation fc_reln, ForkNumber fc_forkno,
							  BlockNumber fc_segno, int fc_oflags);
static MdfdVec *fc__mdfd_getseg(SMgrRelation fc_reln, ForkNumber fc_forkno,
							 BlockNumber fc_blkno, bool fc_skipFsync, int fc_behavior);
static BlockNumber fc__mdnblocks(SMgrRelation fc_reln, ForkNumber fc_forknum,
							  MdfdVec *fc_seg);


/*
 *	mdinit() -- 初始化磁盘存储管理器的私有状态。
 */
void mdinit(void)
{
	MdCxt = AllocSetContextCreate(TopMemoryContext,
								  "MdSmgr",
								  ALLOCSET_DEFAULT_SIZES);
}

/*
 *	mdexists() -- 物理文件是否存在？
 *
 * 注意：对于待删除的残留文件，这将返回真
 */
bool mdexists(SMgrRelation fc_reln, ForkNumber fc_forkNum)
{
	/*
	 * 首先关闭它，以确保我们注意到自从我们打开它以来，如果该分叉已被取消链接。作为一种优化，我们可以在恢复中跳过这一点，因为恢复时已经关闭了在删除关系时。
	 */
	if (!InRecovery)
		mdclose(fc_reln, fc_forkNum);

	return (fc_mdopenfork(fc_reln, fc_forkNum, EXTENSION_RETURN_NULL) != NULL);
}

/*
 *	mdcreate() -- 在磁盘上创建一个新的关系。
 *
 * 如果 isRedo 为真，关系已经存在也是可以的。
 */
void mdcreate(SMgrRelation fc_reln, ForkNumber fc_forkNum, bool fc_isRedo)
{
	MdfdVec    *fc_mdfd;
	char	   *fc_path;
	File		fc_fd;

	if (fc_isRedo && fc_reln->md_num_open_segs[fc_forkNum] > 0)
		return;					/* 已经创建并打开... */

	Assert(fc_reln->md_num_open_segs[fc_forkNum] == 0);

	/*
	 * 我们可能第一次在此数据库中使用目标表空间，因此如有需要，创建每个数据库的子目录。
	 *
	 * XXX 这在模块分层上是一个相当丑陋的违反，但这似乎是放置检查的最佳位置。也许 TablespaceCreateDbspace 应该在这里，而不是在 commands/tablespace.c？但这将意味着导入 smgr.c 不应知道的很多东西。
	 */
	TablespaceCreateDbspace(fc_reln->smgr_rnode.node.spcNode,
							fc_reln->smgr_rnode.node.dbNode,
							fc_isRedo);

	fc_path = relpath(fc_reln->smgr_rnode, fc_forkNum);

	fc_fd = PathNameOpenFile(fc_path, O_RDWR | O_CREAT | O_EXCL | PG_BINARY);

	if (fc_fd < 0)
	{
		int			fc_save_errno = errno;

		if (fc_isRedo)
			fc_fd = PathNameOpenFile(fc_path, O_RDWR | PG_BINARY);
		if (fc_fd < 0)
		{
			/* 确保报告 create 报告的错误，而不是 open */
			errno = fc_save_errno;
			ereport(ERROR,
					(errcode_for_file_access(),
					 errmsg("could not create file \"%s\": %m", fc_path)));
		}
	}

	pfree(fc_path);

	fc__fdvec_resize(fc_reln, fc_forkNum, 1);
	fc_mdfd = &fc_reln->md_seg_fds[fc_forkNum][0];
	fc_mdfd->mdfd_vfd = fc_fd;
	fc_mdfd->mdfd_segno = 0;

	if (!SmgrIsTemp(fc_reln))
		fc_register_dirty_segment(fc_reln, fc_forkNum, fc_mdfd);
}

/*
 *	mdunlink() -- 解链接一个关系。
 *
 * 请注意，我们传递了一个 RelFileNodeBackend --- 在调用这个时，
 * 将不再有 SMgrRelation 哈希表条目。
 *
 * forkNum 可以是要删除的特定 fork 的 fork 编号，或 InvalidForkNumber
 * 用于删除所有 forks。
 *
 * 对于常规关系，我们不解链接关系的第一个段文件，
 * 而只是将其截断为零长度，并记录一个请求，在下一个检查点之后解链接它。
 * 但是，额外的段可以立即解链接。
 * 保留空文件防止该 relfilenode 编号被重复使用。
 * 这个场景防止我们的是：
 * 1. 我们删除一个关系（并提交，并实际删除它的文件）。
 * 2. 我们创建一个新关系，碰巧获得与刚删除的关系相同的 relfilenode
 *	  （OID 必须已经回绕才能发生那种情况）。
 * 3. 我们在下一个检查点发生之前崩溃。
 * 在重放期间，我们将删除文件然后重新创建它，如果文件内容
 * 通过后续的 WAL 条目重新填充，这是可以的。
 * 但是如果我们没有 WAL 记录插入，而是依赖于在填充后 fsync
 * 文件（如我们在 wal_level=minimal 中所做），文件的内容将永远丢失。
 * 通过在下一个检查点之前保留空文件，我们防止 relfilenode 编号的重新分配
 * 直到安全为止，因为 relfilenode 分配会跳过任何现有文件。
 *
 * 任何额外的段首先被截断，然后解链接。截断的原因是其他后端
 * 可能仍然在 smgr 级别持有这些的打开文件描述符，因此内核尚无法删除文件。
 * 尽管如此，我们希望立即回收磁盘空间。
 *
 * 不过，对于临时关系，我们无需进行这一系列操作，因为
 * 我们从未为临时关系生成 WAL 条目，因此临时关系对
 * 已接管其 relfilenode 编号的常规关系的健康没有威胁。
 * 临时关系和常规关系具有不同的文件命名模式这一事实提供了额外的安全性。
 * 其他后端也不应对它们保持打开的文件描述符。
 *
 * 在执行二进制升级时我们也不这样做。在这种情况下没有重复使用
 * 的危害，因为崩溃后甚至是简单的错误，升级失败，整个集群
 * 必须从头开始重建。
 * 此外，立即从磁盘中移除文件非常重要，因为我们可能会重新使用相同的
 * relfilenode 编号。
 *
 * 上述所有内容仅适用于关系的主 fork；其他 forks
 * 可以立即删除，因为它们不需要阻止 relfilenode 编号被回收。
 * 此外，我们没有仔细跟踪其他 forks 是否已创建，而只是尝试
 * 无条件地解链接它们；因此我们永远不应抱怨 ENOENT。
 *
 * 如果 isRedo 为真，关系已经消失也就不足为奇。
 * 此外，我们应立即删除文件，而不是排队请求稍后删除，
 * 因为在重做过程中没有可能创建冲突关系。
 *
 * 注意：我们目前根本不警告 ENOENT。我们可以在主 fork，
 * 非 isRedo 的情况下发出警告，但似乎不值得麻烦。
 *
 * 注意：任何失败应报告为警告而不是错误，因为
 * 当调用这些时，我们通常不再处于事务中。
 */
void mdunlink(RelFileNodeBackend fc_rnode, ForkNumber fc_forkNum, bool fc_isRedo)
{
	/* 现在进行每个 fork 的工作 */
	if (fc_forkNum == InvalidForkNumber)
	{
		for (fc_forkNum = 0; fc_forkNum <= MAX_FORKNUM; fc_forkNum++)
			fc_mdunlinkfork(fc_rnode, fc_forkNum, fc_isRedo);
	}
	else
		fc_mdunlinkfork(fc_rnode, fc_forkNum, fc_isRedo);
}

/*
 * 截断文件以释放磁盘空间。
 */
static int fc_do_truncate(const char *fc_path)
{
	int			fc_save_errno;
	int			fc_ret;

	fc_ret = pg_truncate(fc_path, 0);

	/* 在这里记录警告以避免调用者中的重复。 */
	if (fc_ret < 0 && errno != ENOENT)
	{
		fc_save_errno = errno;
		ereport(WARNING,
				(errcode_for_file_access(),
				 errmsg("could not truncate file \"%s\": %m", fc_path)));
		errno = fc_save_errno;
	}

	return fc_ret;
}

static void fc_mdunlinkfork(RelFileNodeBackend fc_rnode, ForkNumber fc_forkNum, bool fc_isRedo)
{
	char	   *fc_path;
	int			fc_ret;
	int			fc_save_errno;

	fc_path = relpath(fc_rnode, fc_forkNum);

	/*
	 * 截断然后解链接第一个段，或仅注册请求
	 * 稍后解链接它，如 mdunlink() 注释中所述。
	 */
	if (fc_isRedo || IsBinaryUpgrade || fc_forkNum != MAIN_FORKNUM ||
		RelFileNodeBackendIsTemp(fc_rnode))
	{
		if (!RelFileNodeBackendIsTemp(fc_rnode))
		{
			/* 防止其他后端的文件描述符占用磁盘空间 */
			fc_ret = fc_do_truncate(fc_path);

			/* 忘记第一个段的任何挂起同步请求 */
			fc_save_errno = errno;
			fc_register_forget_request(fc_rnode, fc_forkNum, 0 /* 第一个段 */ );
			errno = fc_save_errno;
		}
		else
			fc_ret = 0;

		/* 接下来解链接文件，除非发现它已缺失 */
		if (fc_ret >= 0 || errno != ENOENT)
		{
			fc_ret = unlink(fc_path);
			if (fc_ret < 0 && errno != ENOENT)
			{
				fc_save_errno = errno;
				ereport(WARNING,
						(errcode_for_file_access(),
						 errmsg("could not remove file \"%s\": %m", fc_path)));
				errno = fc_save_errno;
			}
		}
	}
	else
	{
		/* 防止其他后端的文件描述符占用磁盘空间 */
		fc_ret = fc_do_truncate(fc_path);

		/* 注册请求稍后解链接第一个段 */
		fc_save_errno = errno;
		fc_register_unlink_segment(fc_rnode, fc_forkNum, 0 /* 第一个段 */ );
		errno = fc_save_errno;
	}

	/*
	 * 删除任何额外的段。
	 *
	 * 请注意，因为我们循环直到获取 ENOENT，
	 * 所以我们将正确删除所有非活动段以及活动段。
	 * 理想情况下，我们会继续循环直到获取确切的 errno，
	 * 但如果问题是目录范围的（例如，如果我们
	 * 突然无法读取数据目录本身），这就存在无限循环的风险。
	 * 我们妥协通过在遇到非 ENOENT 的截断错误后继续，但在任
	 * 何解链接错误后停止。如果确实存在目录范围的问题，额外的
	 * 解链接尝试也无济于事。
	 */
	if (fc_ret >= 0 || errno != ENOENT)
	{
		char	   *fc_segpath = (char *) palloc(strlen(fc_path) + 12);
		BlockNumber fc_segno;

		for (fc_segno = 1;; fc_segno++)
		{
			sprintf(fc_segpath, "%s.%u", fc_path, fc_segno);

			if (!RelFileNodeBackendIsTemp(fc_rnode))
			{
				/*
				 * 防止其他后端的文件描述符占用磁盘
				 * 空间。如果我们看到 ENOENT，我们就完成了。
				 */
				if (fc_do_truncate(fc_segpath) < 0 && errno == ENOENT)
					break;

				
/*
				 * 忘记在我们尝试解除链接之前对此段的任何挂起同步请求。
				 */
				fc_register_forget_request(fc_rnode, fc_forkNum, fc_segno);
			}

			if (unlink(fc_segpath) < 0)
			{
				/* 预期在最后一个段之后返回ENOENT... */
				if (errno != ENOENT)
					ereport(WARNING,
							(errcode_for_file_access(),
							 errmsg("could not remove file \"%s\": %m", fc_segpath)));
				break;
			}
		}
		pfree(fc_segpath);
	}

	pfree(fc_path);
}

/*
 *	mdextend() -- 向指定关系添加一个块。
 *
 *		其语义几乎与mdwrite()相同：在指定位置写入。
 *		但是，这用于扩展关系的情况（即，块号位于当前
 *		EOF或之后）。请注意，我们假设在当前EOF之外
 *		写入一个块会导致介于其间的文件空间填充为零。
 */
void mdextend(SMgrRelation fc_reln, ForkNumber fc_forknum, BlockNumber fc_blocknum,
		 char *fc_buffer, bool fc_skipFsync)
{
	off_t		fc_seekpos;
	int			fc_nbytes;
	MdfdVec    *fc_v;

	/* 这个断言通常开着太昂贵... */
#ifdef CHECK_WRITE_VS_EXTEND
	Assert(fc_blocknum >= mdnblocks(fc_reln, fc_forknum));
#endif

	/*
	 * 如果一个关系成功增长到2^32-1个块，则拒绝再次扩展它---
	 * 我们不能创建一个其编号实际上是InvalidBlockNumber的块。
	 * （请注意，由于bufmgr.c中的上游检查，此失败应该是不可到达的。）
	 */
	if (fc_blocknum == InvalidBlockNumber)
		ereport(ERROR,
				(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
				 errmsg("cannot extend file \"%s\" beyond %u blocks",
						relpath(fc_reln->smgr_rnode, fc_forknum),
						InvalidBlockNumber)));

	fc_v = fc__mdfd_getseg(fc_reln, fc_forknum, fc_blocknum, fc_skipFsync, EXTENSION_CREATE);

	fc_seekpos = (off_t) BLCKSZ * (fc_blocknum % ((BlockNumber) RELSEG_SIZE));

	Assert(fc_seekpos < (off_t) BLCKSZ * RELSEG_SIZE);

#ifdef FDD
	if(fc_forknum == MAIN_FORKNUM && (FDB_ENCRYPT_TYPE_NONE != g_tde_encrypt) && fc_reln->smgr_rnode.node.relNode >= MIN_USER_TABLE_OID && fc_reln->smgr_rnode.backend == InvalidBackendId)
	{
		fc_buffer = (char*)fdb_encodePage(fc_buffer, fc_blocknum);
	}
#endif
	if ((fc_nbytes = FileWrite(fc_v->mdfd_vfd, fc_buffer, BLCKSZ, fc_seekpos, WAIT_EVENT_DATA_FILE_EXTEND)) != BLCKSZ)
	{
		if (fc_nbytes < 0)
			ereport(ERROR,
					(errcode_for_file_access(),
					 errmsg("could not extend file \"%s\": %m",
							FilePathName(fc_v->mdfd_vfd)),
					 errhint("Check free disk space.")));
		/* 短写：适当投诉 */
		ereport(ERROR,
				(errcode(ERRCODE_DISK_FULL),
				 errmsg("could not extend file \"%s\": wrote only %d of %d bytes at block %u",
						FilePathName(fc_v->mdfd_vfd),
						fc_nbytes, BLCKSZ, fc_blocknum),
				 errhint("Check free disk space.")));
	}

	if (!fc_skipFsync && !SmgrIsTemp(fc_reln))
		fc_register_dirty_segment(fc_reln, fc_forknum, fc_v);

	Assert(fc__mdnblocks(fc_reln, fc_forknum, fc_v) <= ((BlockNumber) RELSEG_SIZE));
}

/*
 *	mdopenfork() -- 打开指定关系的一个分支。
 *
 * 请注意，当有多个段时，我们只打开第一个段。
 *
 * 如果第一个段不存在，则根据“行为”要么ereport，要么返回NULL。
 * 我们将EXTENSION_CREATE视为与EXTENSION_FAIL相同；
 * EXTENSION_CREATE意味着可以扩展现有关系，而不是凭空创造一个。
 */
static MdfdVec * fc_mdopenfork(SMgrRelation fc_reln, ForkNumber fc_forknum, int fc_behavior)
{
	MdfdVec    *fc_mdfd;
	char	   *fc_path;
	File		fc_fd;

	/* 已经打开时不需要工作 */
	if (fc_reln->md_num_open_segs[fc_forknum] > 0)
		return &fc_reln->md_seg_fds[fc_forknum][0];

	fc_path = relpath(fc_reln->smgr_rnode, fc_forknum);

	fc_fd = PathNameOpenFile(fc_path, O_RDWR | PG_BINARY);

	if (fc_fd < 0)
	{
		if ((fc_behavior & EXTENSION_RETURN_NULL) &&
			FILE_POSSIBLY_DELETED(errno))
		{
			pfree(fc_path);
			return NULL;
		}
		ereport(ERROR,
				(errcode_for_file_access(),
				 errmsg("could not open file \"%s\": %m", fc_path)));
	}

	pfree(fc_path);

	fc__fdvec_resize(fc_reln, fc_forknum, 1);
	fc_mdfd = &fc_reln->md_seg_fds[fc_forknum][0];
	fc_mdfd->mdfd_vfd = fc_fd;
	fc_mdfd->mdfd_segno = 0;

	Assert(fc__mdnblocks(fc_reln, fc_forknum, fc_mdfd) <= ((BlockNumber) RELSEG_SIZE));

	return fc_mdfd;
}

/*
 *  mdopen() -- 初始化新打开的关系。
 */
void mdopen(SMgrRelation fc_reln)
{
	/* 标记为未打开 */
	for (int fc_forknum = 0; fc_forknum <= MAX_FORKNUM; fc_forknum++)
		fc_reln->md_num_open_segs[fc_forknum] = 0;
}

/*
 *	mdclose() -- 关闭指定关系，如果尚未关闭。
 */
void mdclose(SMgrRelation fc_reln, ForkNumber fc_forknum)
{
	int			fc_nopensegs = fc_reln->md_num_open_segs[fc_forknum];

	/* 已关闭时不需要工作 */
	if (fc_nopensegs == 0)
		return;

	/* 从末尾开始关闭段 */
	while (fc_nopensegs > 0)
	{
		MdfdVec    *fc_v = &fc_reln->md_seg_fds[fc_forknum][fc_nopensegs - 1];

		FileClose(fc_v->mdfd_vfd);
		fc__fdvec_resize(fc_reln, fc_forknum, fc_nopensegs - 1);
		fc_nopensegs--;
	}
}

/*
 *	mdprefetch() -- 启动指定关系的特定块的异步读取
 */
bool mdprefetch(SMgrRelation fc_reln, ForkNumber fc_forknum, BlockNumber fc_blocknum)
{
#ifdef USE_PREFETCH
	off_t		fc_seekpos;
	MdfdVec    *fc_v;

	fc_v = fc__mdfd_getseg(fc_reln, fc_forknum, fc_blocknum, false,
					 InRecovery ? EXTENSION_RETURN_NULL : EXTENSION_FAIL);
	if (fc_v == NULL)
		return false;

	fc_seekpos = (off_t) BLCKSZ * (fc_blocknum % ((BlockNumber) RELSEG_SIZE));

	Assert(fc_seekpos < (off_t) BLCKSZ * RELSEG_SIZE);

	(void) FilePrefetch(fc_v->mdfd_vfd, fc_seekpos, BLCKSZ, WAIT_EVENT_DATA_FILE_PREFETCH);
#endif							/* USE_PREFETCH */

	return true;
}

/*
 * mdwriteback() -- 告诉内核将页面写回存储。
 *
 * 这是接受一系列块，因为一次刷新多个页面比单独
 * 刷新要高效得多。
 */
void mdwriteback(SMgrRelation fc_reln, ForkNumber fc_forknum,
			BlockNumber fc_blocknum, BlockNumber fc_nblocks)
{
	/*
	 * 尽可能少地发出刷新请求；不过，在段边界处必须分裂，
	 * 因为那些实际上是单独的文件。
	 */
	while (fc_nblocks > 0)
	{
		BlockNumber fc_nflush = fc_nblocks;
		off_t		fc_seekpos;
		MdfdVec    *fc_v;
		int			fc_segnum_start,
					fc_segnum_end;

		fc_v = fc__mdfd_getseg(fc_reln, fc_forknum, fc_blocknum, true /* 未使用 */ ,
						 EXTENSION_DONT_OPEN);

		/*
		 * 我们可能正在刷新已被移除关系的缓冲区，这没关系，
		 * 只需忽略这种情况。如果段文件尚未打开
		 * （即来自最近的mdwrite()），那么我们不想重新打开它，
		 * 以避免与PROCSIGNAL_BARRIER_SMGRRELEASE竞争，
		 * 这可能会让我们得到一个即将被解除链接的文件的描述符。
		 */
		if (!fc_v)
			return;

		/* 计算当前段内的偏移量 */
		fc_segnum_start = fc_blocknum / RELSEG_SIZE;

		/* 计算当前段内所需写入的数量 */
		fc_segnum_end = (fc_blocknum + fc_nblocks - 1) / RELSEG_SIZE;
		if (fc_segnum_start != fc_segnum_end)
			fc_nflush = RELSEG_SIZE - (fc_blocknum % ((BlockNumber) RELSEG_SIZE));

		Assert(fc_nflush >= 1);
		Assert(fc_nflush <= fc_nblocks);

		fc_seekpos = (off_t) BLCKSZ * (fc_blocknum % ((BlockNumber) RELSEG_SIZE));

		FileWriteback(fc_v->mdfd_vfd, fc_seekpos, (off_t) BLCKSZ * fc_nflush, WAIT_EVENT_DATA_FILE_FLUSH);

		fc_nblocks -= fc_nflush;
		fc_blocknum += fc_nflush;
	}
}

/*
 *	mdread() -- 从关系中读取指定块。
 */
void mdread(SMgrRelation fc_reln, ForkNumber fc_forknum, BlockNumber fc_blocknum,
	   char *fc_buffer)
{
	off_t		fc_seekpos;
	int			fc_nbytes;
	MdfdVec    *fc_v;

	TRACE_POSTGRESQL_SMGR_MD_READ_START(fc_forknum, fc_blocknum,
										fc_reln->smgr_rnode.node.spcNode,
										fc_reln->smgr_rnode.node.dbNode,
										fc_reln->smgr_rnode.node.relNode,
										fc_reln->smgr_rnode.backend);

	fc_v = fc__mdfd_getseg(fc_reln, fc_forknum, fc_blocknum, false,
					 EXTENSION_FAIL | EXTENSION_CREATE_RECOVERY);

	fc_seekpos = (off_t) BLCKSZ * (fc_blocknum % ((BlockNumber) RELSEG_SIZE));

	Assert(fc_seekpos < (off_t) BLCKSZ * RELSEG_SIZE);

	fc_nbytes = FileRead(fc_v->mdfd_vfd, fc_buffer, BLCKSZ, fc_seekpos, WAIT_EVENT_DATA_FILE_READ);

	TRACE_POSTGRESQL_SMGR_MD_READ_DONE(fc_forknum, fc_blocknum,
									   fc_reln->smgr_rnode.node.spcNode,
									   fc_reln->smgr_rnode.node.dbNode,
									   fc_reln->smgr_rnode.node.relNode,
									   fc_reln->smgr_rnode.backend,
									   fc_nbytes,
									   BLCKSZ);

	if (fc_nbytes != BLCKSZ)
	{
		if (fc_nbytes < 0)
			ereport(ERROR,
					(errcode_for_file_access(),
					 errmsg("could not read block %u in file \"%s\": %m",
							fc_blocknum, FilePathName(fc_v->mdfd_vfd))));

		/*
		 * 短读：我们位于或超过 EOF，或者在 EOF 处读取了部分块。
		 * 通常这是一个错误；上层不应该尝试读取一个不存在的块。
		 * 然而，如果 zero_damaged_pages 开启或我们处于恢复状态，
		 * 我们应该返回零而不抱怨。这允许例如尝试
		 * 更新一个后续被截断的块的情况。
		 */
		if (zero_damaged_pages || InRecovery)
			MemSet(fc_buffer, 0, BLCKSZ);
		else
			ereport(ERROR,
					(errcode(ERRCODE_DATA_CORRUPTED),
					 errmsg("could not read block %u in file \"%s\": read only %d of %d bytes",
							fc_blocknum, FilePathName(fc_v->mdfd_vfd),
							fc_nbytes, BLCKSZ)));
	}
#ifdef FDD
	else if(fc_forknum == MAIN_FORKNUM && fc_reln->smgr_rnode.node.relNode >= MIN_USER_TABLE_OID)
	{
		fdb_decodePage(fc_buffer, fc_blocknum);
	}
#endif
}

/*
 *	mdwrite() -- 在适当的位置写入提供的块。
 *
 *		此操作仅用于更新已存在的关系块（即，在当前 EOF 之前的那些）。
 *		要扩展一个关系，请使用 mdextend()。
 */
void mdwrite(SMgrRelation fc_reln, ForkNumber fc_forknum, BlockNumber fc_blocknum,
		char *fc_buffer, bool fc_skipFsync)
{
	off_t		fc_seekpos;
	int			fc_nbytes;
	MdfdVec    *fc_v;

	/* 这个断言通常开着太昂贵... */
#ifdef CHECK_WRITE_VS_EXTEND
	Assert(fc_blocknum < mdnblocks(fc_reln, fc_forknum));
#endif

	TRACE_POSTGRESQL_SMGR_MD_WRITE_START(fc_forknum, fc_blocknum,
										 fc_reln->smgr_rnode.node.spcNode,
										 fc_reln->smgr_rnode.node.dbNode,
										 fc_reln->smgr_rnode.node.relNode,
										 fc_reln->smgr_rnode.backend);

	fc_v = fc__mdfd_getseg(fc_reln, fc_forknum, fc_blocknum, fc_skipFsync,
					 EXTENSION_FAIL | EXTENSION_CREATE_RECOVERY);

	fc_seekpos = (off_t) BLCKSZ * (fc_blocknum % ((BlockNumber) RELSEG_SIZE));

	Assert(fc_seekpos < (off_t) BLCKSZ * RELSEG_SIZE);
#ifdef FDD
	// 所有小于 MIN_USER_TABLE_OID 表明是系统表，我们不对系统表加密；backend为InvalidBackendId,表明是临时表，也不会对其加密
	if(fc_forknum == MAIN_FORKNUM && (FDB_ENCRYPT_TYPE_NONE != g_tde_encrypt) && fc_reln->smgr_rnode.node.relNode >= MIN_USER_TABLE_OID && fc_reln->smgr_rnode.backend == InvalidBackendId)
	{
		fc_buffer = (char*)fdb_encodePage(fc_buffer,fc_blocknum);
	}
#endif
	fc_nbytes = FileWrite(fc_v->mdfd_vfd, fc_buffer, BLCKSZ, fc_seekpos, WAIT_EVENT_DATA_FILE_WRITE);

	TRACE_POSTGRESQL_SMGR_MD_WRITE_DONE(fc_forknum, fc_blocknum,
										fc_reln->smgr_rnode.node.spcNode,
										fc_reln->smgr_rnode.node.dbNode,
										fc_reln->smgr_rnode.node.relNode,
										fc_reln->smgr_rnode.backend,
										fc_nbytes,
										BLCKSZ);

	if (fc_nbytes != BLCKSZ)
	{
		if (fc_nbytes < 0)
			ereport(ERROR,
					(errcode_for_file_access(),
					 errmsg("could not write block %u in file \"%s\": %m",
							fc_blocknum, FilePathName(fc_v->mdfd_vfd))));
		/* 短写：适当投诉 */
		ereport(ERROR,
				(errcode(ERRCODE_DISK_FULL),
				 errmsg("could not write block %u in file \"%s\": wrote only %d of %d bytes",
						fc_blocknum,
						FilePathName(fc_v->mdfd_vfd),
						fc_nbytes, BLCKSZ),
				 errhint("Check free disk space.")));
	}

	if (!fc_skipFsync && !SmgrIsTemp(fc_reln))
		fc_register_dirty_segment(fc_reln, fc_forknum, fc_v);
}

/*
 *	mdnblocks() -- 获取存储在关系中的块数。
 *
 *		重要的副作用：关系的所有活动段都被打开并
 *		添加到 md_seg_fds 数组中。如果该例程尚未被调用，
 *		那么数组中只存在实际接触的最后一个段之前的段。
 */
BlockNumber mdnblocks(SMgrRelation fc_reln, ForkNumber fc_forknum)
{
	MdfdVec    *fc_v;
	BlockNumber fc_nblocks;
	BlockNumber fc_segno;

	fc_mdopenfork(fc_reln, fc_forknum, EXTENSION_FAIL);

	/* mdopen 已打开第一个段 */
	Assert(fc_reln->md_num_open_segs[fc_forknum] > 0);

	/*
	 * 从最后一个打开的段开始，以避免重复的查找。我们之前
	 * 已验证这些段的确恰好是 RELSEG_SIZE 长，且每次检查都是无用的。
	 *
	 * 注意：这个假设只有在另一后端已截断该关系的情况下才会错误。
	 * 我们依赖更高层次的代码来处理这个场景，通过关闭和重新打开 md fd，
	 * 这通过 relcache 刷新处理。（由于检查点不参与
	 * relcache 刷新，它可能有针对非活动段的段条目；
	 * 这没关系，因为检查点从不需要计算关系的
	 * 大小。）
	 */
	fc_segno = fc_reln->md_num_open_segs[fc_forknum] - 1;
	fc_v = &fc_reln->md_seg_fds[fc_forknum][fc_segno];

	for (;;)
	{
		fc_nblocks = fc__mdnblocks(fc_reln, fc_forknum, fc_v);
		if (fc_nblocks > ((BlockNumber) RELSEG_SIZE))
			elog(FATAL, "segment too big");
		if (fc_nblocks < ((BlockNumber) RELSEG_SIZE))
			return (fc_segno * ((BlockNumber) RELSEG_SIZE)) + fc_nblocks;

		/*
		 * 如果段正好是 RELSEG_SIZE，则前进到下一个。
		 */
		fc_segno++;

		/*
		 * 我们以前在这里传递 O_CREAT，但这有一个缺点，
		 * 它可能会创建一个在某些操作系统故障中消失的段。
		 * 在这种情况下，创建段会破坏 _mdfd_getseg 在访问丢失段时
		 * 发现并报告错误的尝试。
		 */
		fc_v = fc__mdfd_openseg(fc_reln, fc_forknum, fc_segno, 0);
		if (fc_v == NULL)
			return fc_segno * ((BlockNumber) RELSEG_SIZE);
	}
}

/*
 *	mdtruncate() -- 将关系截断为指定数量的块。
 *
 * 保证不分配内存，因此可以在关键区段中使用。
 * 调用者必须在保持足够的锁以防止关系大小变化的情况下调用
 * smgrnblocks() 来获取 curnblk，并且未在此关系中使用任何 smgr
 * 函数或在之间处理中断。这确保我们已打开所有活动段，
 * 以便截断循环会处理它们所有！
 */
void mdtruncate(SMgrRelation fc_reln, ForkNumber fc_forknum,
		   BlockNumber fc_curnblk, BlockNumber fc_nblocks)
{
	BlockNumber fc_priorblocks;
	int			fc_curopensegs;

	if (fc_nblocks > fc_curnblk)
	{
		/* 无效请求...但如果处于恢复状态则没有抱怨 */
		if (InRecovery)
			return;
		ereport(ERROR,
				(errmsg("could not truncate file \"%s\" to %u blocks: it's only %u blocks now",
						relpath(fc_reln->smgr_rnode, fc_forknum),
						fc_nblocks, fc_curnblk)));
	}
	if (fc_nblocks == fc_curnblk)
		return;					/* 无工作 */

	/*
	 * 从最后一个段开始截断段。 从末尾开始可以使
	 * 管理 fd 数组的内存更容易，如果出现错误。
	 */
	fc_curopensegs = fc_reln->md_num_open_segs[fc_forknum];
	while (fc_curopensegs > 0)
	{
		MdfdVec    *fc_v;

		fc_priorblocks = (fc_curopensegs - 1) * RELSEG_SIZE;

		fc_v = &fc_reln->md_seg_fds[fc_forknum][fc_curopensegs - 1];

		if (fc_priorblocks > fc_nblocks)
		{
			/*
			 * 此段不再处于活动状态。我们截断文件，但不删除它，
			 * 原因在头文件注释中已解释。
			 */
			if (FileTruncate(fc_v->mdfd_vfd, 0, WAIT_EVENT_DATA_FILE_TRUNCATE) < 0)
				ereport(ERROR,
						(errcode_for_file_access(),
						 errmsg("could not truncate file \"%s\": %m",
								FilePathName(fc_v->mdfd_vfd))));

			if (!SmgrIsTemp(fc_reln))
				fc_register_dirty_segment(fc_reln, fc_forknum, fc_v);

			/* 我们永远不会丢弃第一个段 */
			Assert(fc_v != &fc_reln->md_seg_fds[fc_forknum][0]);

			FileClose(fc_v->mdfd_vfd);
			fc__fdvec_resize(fc_reln, fc_forknum, fc_curopensegs - 1);
		}
		else if (fc_priorblocks + ((BlockNumber) RELSEG_SIZE) > fc_nblocks)
		{
			/*
			 * 这是我们希望保留的最后一个段。将文件截断为
			 * 正确的长度。注意：如果 nblocks 正好是 RELSEG_SIZE 的
			 * 整数倍，则我们将第 K+1 个段截断为 0 长度，但保留它。
			 * 这遵循头文件注释中给出的不变式。
			 */
			BlockNumber fc_lastsegblocks = fc_nblocks - fc_priorblocks;

			if (FileTruncate(fc_v->mdfd_vfd, (off_t) fc_lastsegblocks * BLCKSZ, WAIT_EVENT_DATA_FILE_TRUNCATE) < 0)
				ereport(ERROR,
						(errcode_for_file_access(),
						 errmsg("could not truncate file \"%s\" to %u blocks: %m",
								FilePathName(fc_v->mdfd_vfd),
								fc_nblocks)));
			if (!SmgrIsTemp(fc_reln))
				fc_register_dirty_segment(fc_reln, fc_forknum, fc_v);
		}
		else
		{
			/*
			 * 我们仍然需要这个段，因此对这个和任何早期段没有操作。
			 */
			break;
		}
		fc_curopensegs--;
	}
}

/*
 *	mdimmedsync() -- 立即将关系同步到稳定存储。
 *
 * 注意只有已经发出的写入被同步；此例程对
 * 可能存在于缓冲区管理器中的脏缓冲区一无所知。我们
 * 同步活动和非活动段；smgrDoPendingSyncs() 依赖于此。
 * 考虑一个跳过 WAL 的关系。假设一个检查点同步了一些段的块，
 * 然后 mdtruncate() 将该段设为非活动状态。如果我们在下一个
 * 检查点同步新不活跃段之前崩溃，该段可能会在恢复后存活，
 * 将不需要的数据重新引入表中。
 */
void mdimmedsync(SMgrRelation fc_reln, ForkNumber fc_forknum)
{
	int			fc_segno;
	int			fc_min_inactive_seg;

	/*
	 * 注意：mdnblocks 确保我们已打开所有活动段，以便 fsync 循环能处理它们所有！
	 */
	mdnblocks(fc_reln, fc_forknum);

	fc_min_inactive_seg = fc_segno = fc_reln->md_num_open_segs[fc_forknum];

	/*
	 * 暂时打开非活动段，然后在同步后关闭它们。
	 * 在 fsync() 错误后可能会有一些非活动段仍然打开，
	 * 但这无害。我们不打算清理它们，以免带来
	 * 进一步麻烦。下一个 mdclose() 很快会关闭它们。
	 */
	while (fc__mdfd_openseg(fc_reln, fc_forknum, fc_segno, 0) != NULL)
		fc_segno++;

	while (fc_segno > 0)
	{
		MdfdVec    *fc_v = &fc_reln->md_seg_fds[fc_forknum][fc_segno - 1];

		if (FileSync(fc_v->mdfd_vfd, WAIT_EVENT_DATA_FILE_IMMEDIATE_SYNC) < 0)
			ereport(data_sync_elevel(ERROR),
					(errcode_for_file_access(),
					 errmsg("could not fsync file \"%s\": %m",
							FilePathName(fc_v->mdfd_vfd))));

		/* 立即关闭非活动段 */
		if (fc_segno > fc_min_inactive_seg)
		{
			FileClose(fc_v->mdfd_vfd);
			fc__fdvec_resize(fc_reln, fc_forknum, fc_segno - 1);
		}

		fc_segno--;
	}
}


/*
 * register_dirty_segment() -- 标记关系段需要 fsync
 *
 * 如果有本地待处理操作表，只需为 ProcessSyncRequests 处理稍后进行一个条目。否则，尝试将 fsync 请求传递给检查点进程。如果失败，返回之前仅在本地执行 fsync（我们希望这不会经常发生，成为性能问题）。
 */
static void fc_register_dirty_segment(SMgrRelation fc_reln, ForkNumber fc_forknum, MdfdVec *fc_seg)
{
	FileTag		fc_tag;

	INIT_MD_FILETAG(fc_tag, fc_reln->smgr_rnode.node, fc_forknum, fc_seg->mdfd_segno);

	/* 临时关系不应被 fsync */
	Assert(!SmgrIsTemp(fc_reln));

	if (!RegisterSyncRequest(&fc_tag, SYNC_REQUEST, false /* retryOnError */ ))
	{
		ereport(DEBUG1,
				(errmsg_internal("could not forward fsync request because request queue is full")));

		if (FileSync(fc_seg->mdfd_vfd, WAIT_EVENT_DATA_FILE_SYNC) < 0)
			ereport(data_sync_elevel(ERROR),
					(errcode_for_file_access(),
					 errmsg("could not fsync file \"%s\": %m",
							FilePathName(fc_seg->mdfd_vfd))));
	}
}

/*
 * register_unlink_segment() -- 安排在下一个检查点之后删除一个文件
 */
static void fc_register_unlink_segment(RelFileNodeBackend fc_rnode, ForkNumber fc_forknum,
						BlockNumber fc_segno)
{
	FileTag		fc_tag;

	INIT_MD_FILETAG(fc_tag, fc_rnode.node, fc_forknum, fc_segno);

	/* 不应与临时关系同时使用 */
	Assert(!RelFileNodeBackendIsTemp(fc_rnode));

	RegisterSyncRequest(&fc_tag, SYNC_UNLINK_REQUEST, true /* retryOnError */ );
}

/*
 * register_forget_request() -- 忘记任何关系分支段的 fsync
 */
static void fc_register_forget_request(RelFileNodeBackend fc_rnode, ForkNumber fc_forknum,
						BlockNumber fc_segno)
{
	FileTag		fc_tag;

	INIT_MD_FILETAG(fc_tag, fc_rnode.node, fc_forknum, fc_segno);

	RegisterSyncRequest(&fc_tag, SYNC_FORGET_REQUEST, true /* retryOnError */ );
}

/*
 * ForgetDatabaseSyncRequests -- 忘记数据库的任何 fsync 和 unlink
 */
void ForgetDatabaseSyncRequests(Oid fc_dbid)
{
	FileTag		fc_tag;
	RelFileNode fc_rnode;

	fc_rnode.dbNode = fc_dbid;
	fc_rnode.spcNode = 0;
	fc_rnode.relNode = 0;

	INIT_MD_FILETAG(fc_tag, fc_rnode, InvalidForkNumber, InvalidBlockNumber);

	RegisterSyncRequest(&fc_tag, SYNC_FILTER_REQUEST, true /* retryOnError */ );
}

/*
 * DropRelationFiles -- 删除所有给定关系的文件
 */
void DropRelationFiles(RelFileNode *fc_delrels, int fc_ndelrels, bool fc_isRedo)
{
	SMgrRelation *fc_srels;
	int			fc_i;

	fc_srels = palloc(sizeof(SMgrRelation) * fc_ndelrels);
	for (fc_i = 0; fc_i < fc_ndelrels; fc_i++)
	{
		SMgrRelation fc_srel = smgropen(fc_delrels[fc_i], InvalidBackendId);

		if (fc_isRedo)
		{
			ForkNumber	fc_fork;

			for (fc_fork = 0; fc_fork <= MAX_FORKNUM; fc_fork++)
				XLogDropRelation(fc_delrels[fc_i], fc_fork);
		}
		fc_srels[fc_i] = fc_srel;
	}

	smgrdounlinkall(fc_srels, fc_ndelrels, fc_isRedo);

	for (fc_i = 0; fc_i < fc_ndelrels; fc_i++)
		smgrclose(fc_srels[fc_i]);
	pfree(fc_srels);
}


/*
 *	_fdvec_resize() -- 调整分支的打开段数组大小
 */
static void fc__fdvec_resize(SMgrRelation fc_reln,
			  ForkNumber fc_forknum,
			  int fc_nseg)
{
	if (fc_nseg == 0)
	{
		if (fc_reln->md_num_open_segs[fc_forknum] > 0)
		{
			pfree(fc_reln->md_seg_fds[fc_forknum]);
			fc_reln->md_seg_fds[fc_forknum] = NULL;
		}
	}
	else if (fc_reln->md_num_open_segs[fc_forknum] == 0)
	{
		fc_reln->md_seg_fds[fc_forknum] =
			MemoryContextAlloc(MdCxt, sizeof(MdfdVec) * fc_nseg);
	}
	else if (fc_nseg > fc_reln->md_num_open_segs[fc_forknum])
	{
		/*
		 * 似乎没有必要复杂化代码以摊销
		 * repalloc() 调用。那些调用比 PathNameOpenFile() 或
		 * FileClose() 快得多，并且内存上下文内部有时会避免
		 * 实际的重新分配。
		 */
		fc_reln->md_seg_fds[fc_forknum] =
			repalloc(fc_reln->md_seg_fds[fc_forknum],
					 sizeof(MdfdVec) * fc_nseg);
	}
	else
	{
		/*
		 * 我们不重新分配一个较小的数组，因为我们希望 mdtruncate()
		 * 能够保证不会分配内存，因此可以在临界区中被允许使用。 这意味着数组中
		 * 会浪费一些空间，直到下次添加一个段并重新分配。
		 */
	}

	fc_reln->md_num_open_segs[fc_forknum] = fc_nseg;
}

/*
 * 返回指定关系段的文件名。返回的字符串是 palloc 分配的。
 */
static char * fc__mdfd_segpath(SMgrRelation fc_reln, ForkNumber fc_forknum, BlockNumber fc_segno)
{
	char	   *fc_path,
			   *fc_fullpath;

	fc_path = relpath(fc_reln->smgr_rnode, fc_forknum);

	if (fc_segno > 0)
	{
		fc_fullpath = psprintf("%s.%u", fc_path, fc_segno);
		pfree(fc_path);
	}
	else
		fc_fullpath = fc_path;

	return fc_fullpath;
}

/*
 * 打开指定的关系段，并为其创建一个 MdfdVec 对象。失败时返回 NULL。
 */
static MdfdVec * fc__mdfd_openseg(SMgrRelation fc_reln, ForkNumber fc_forknum, BlockNumber fc_segno,
			  int fc_oflags)
{
	MdfdVec    *fc_v;
	File		fc_fd;
	char	   *fc_fullpath;

	fc_fullpath = fc__mdfd_segpath(fc_reln, fc_forknum, fc_segno);

	/* 打开文件 */
	fc_fd = PathNameOpenFile(fc_fullpath, O_RDWR | PG_BINARY | fc_oflags);

	pfree(fc_fullpath);

	if (fc_fd < 0)
		return NULL;

	/*
	 * 段总是按从最低到最高的顺序打开，因此我们必须
	 * 在末尾添加一个新的段。
	 */
	Assert(fc_segno == fc_reln->md_num_open_segs[fc_forknum]);

	fc__fdvec_resize(fc_reln, fc_forknum, fc_segno + 1);

	/* 填充条目 */
	fc_v = &fc_reln->md_seg_fds[fc_forknum][fc_segno];
	fc_v->mdfd_vfd = fc_fd;
	fc_v->mdfd_segno = fc_segno;

	Assert(fc__mdnblocks(fc_reln, fc_forknum, fc_v) <= ((BlockNumber) RELSEG_SIZE));

	/* 全部完成 */
	return fc_v;
}

/*
 *	_mdfd_getseg() -- 查找包含指定块的关系段。
 *
 * 如果该段不存在，我们会报告错误，返回 NULL，或根据“行为”创建
 * 段。注意：skipFsync 仅在 EXTENSION_CREATE 情况下使用。
 */
static MdfdVec * fc__mdfd_getseg(SMgrRelation fc_reln, ForkNumber fc_forknum, BlockNumber fc_blkno,
			 bool fc_skipFsync, int fc_behavior)
{
	MdfdVec    *fc_v;
	BlockNumber fc_targetseg;
	BlockNumber fc_nextsegno;

	/* 需要指定处理不存在的段的某种方式 */
	Assert(fc_behavior &
		   (EXTENSION_FAIL | EXTENSION_CREATE | EXTENSION_RETURN_NULL |
			EXTENSION_DONT_OPEN));

	fc_targetseg = fc_blkno / ((BlockNumber) RELSEG_SIZE);

	/* 如果是现有并已打开的段，我们完成了 */
	if (fc_targetseg < fc_reln->md_num_open_segs[fc_forknum])
	{
		fc_v = &fc_reln->md_seg_fds[fc_forknum][fc_targetseg];
		return fc_v;
	}

	/* 调用者只希望在我们已经打开它时获取段。 */
	if (fc_behavior & EXTENSION_DONT_OPEN)
		return NULL;

	/*
	 * 目标段尚未打开。遍历所有在最后打开和目标段之间的段。
	 * 这样缺失的段要么引发错误，要么根据
	 * '行为' 创建。开始时使用最后一个打开的段，或者如果之前没有
	 * 打开，则使用第一个段。
	 */
	if (fc_reln->md_num_open_segs[fc_forknum] > 0)
		fc_v = &fc_reln->md_seg_fds[fc_forknum][fc_reln->md_num_open_segs[fc_forknum] - 1];
	else
	{
		fc_v = fc_mdopenfork(fc_reln, fc_forknum, fc_behavior);
		if (!fc_v)
			return NULL;		/* 如果行为与 EXTENSION_RETURN_NULL 相关 */
	}

	for (fc_nextsegno = fc_reln->md_num_open_segs[fc_forknum];
		 fc_nextsegno <= fc_targetseg; fc_nextsegno++)
	{
		BlockNumber fc_nblocks = fc__mdnblocks(fc_reln, fc_forknum, fc_v);
		int			fc_flags = 0;

		Assert(fc_nextsegno == fc_v->mdfd_segno + 1);

		if (fc_nblocks > ((BlockNumber) RELSEG_SIZE))
			elog(FATAL, "segment too big");

		if ((fc_behavior & EXTENSION_CREATE) ||
			(InRecovery && (fc_behavior & EXTENSION_CREATE_RECOVERY)))
		{
			/*
			 * 通常我们只会在得到调用者授权的情况下创建新段
			 * （即，我们在执行 mdextend() 时）。但在进行 WAL
			 * 恢复时，无论如何创建段；这允许诸如
			 * 重放 WAL 数据，这些数据写入了一个后来被删除的
			 * 关系的高编号段。我们希望继续创建段，以便完成重放。
			 *
			 * 我们必须保持不变性，确保最后活动段之前的段大小为
			 * RELSEG_SIZE；因此，如果扩展，如果需要，将它们用零填充。
			 * （这只有在恢复中或调用者以不连续方式扩展
			 * 关系时才会与此相关，但在哈希索引中会发生这种情况。）
			 */
			if (fc_nblocks < ((BlockNumber) RELSEG_SIZE))
			{
				char	   *fc_zerobuf = palloc0(BLCKSZ);

				mdextend(fc_reln, fc_forknum,
						 fc_nextsegno * ((BlockNumber) RELSEG_SIZE) - 1,
						 fc_zerobuf, fc_skipFsync);
				pfree(fc_zerobuf);
			}
			fc_flags = O_CREAT;
		}
		else if (!(fc_behavior & EXTENSION_DONT_CHECK_SIZE) &&
				 fc_nblocks < ((BlockNumber) RELSEG_SIZE))
		{
			/*
			 * 当不扩展（或明确包括截断的段）时，仅在当前段
			 * 恰好是 RELSEG_SIZE 时打开下一个段。如果不是
			 * （这个分支）则返回 NULL 或失败。
			 */
			if (fc_behavior & EXTENSION_RETURN_NULL)
			{
				/*
				 * 一些调用者根据 errno 辨别 _mdfd_getseg()
				 * 返回 NULL 的原因。由于在这种情况下没有涉及
				 * 失败的系统调用，因此将 errno 显式设置为
				 * ENOENT，因为这似乎是最接近的解释。
				 */
				errno = ENOENT;
				return NULL;
			}

			ereport(ERROR,
					(errcode_for_file_access(),
					 errmsg("could not open file \"%s\" (target block %u): previous segment is only %u blocks",
							fc__mdfd_segpath(fc_reln, fc_forknum, fc_nextsegno),
							fc_blkno, fc_nblocks)));
		}

		fc_v = fc__mdfd_openseg(fc_reln, fc_forknum, fc_nextsegno, fc_flags);

		if (fc_v == NULL)
		{
			if ((fc_behavior & EXTENSION_RETURN_NULL) &&
				FILE_POSSIBLY_DELETED(errno))
				return NULL;
			ereport(ERROR,
					(errcode_for_file_access(),
					 errmsg("could not open file \"%s\" (target block %u): %m",
							fc__mdfd_segpath(fc_reln, fc_forknum, fc_nextsegno),
							fc_blkno)));
		}
	}

	return fc_v;
}

/*
 * 获取单个磁盘文件中存在的块数
 */
static BlockNumber fc__mdnblocks(SMgrRelation fc_reln, ForkNumber fc_forknum, MdfdVec *fc_seg)
{
	off_t		fc_len;

	fc_len = FileSize(fc_seg->mdfd_vfd);
	if (fc_len < 0)
		ereport(ERROR,
				(errcode_for_file_access(),
				 errmsg("could not seek to end of file \"%s\": %m",
						FilePathName(fc_seg->mdfd_vfd))));
	/* 注意此计算将忽略 EOF 处的任何部分块 */
	return (BlockNumber) (fc_len / BLCKSZ);
}

/*
 * 根据文件标签将文件同步到磁盘。将路径写入输出
 * 缓冲区，以便调用者可以在错误消息中使用它。
 *
 * 成功时返回 0，失败时返回 -1，并设置 errno。
 */
int mdsyncfiletag(const FileTag *fc_ftag, char *fc_path)
{
	SMgrRelation fc_reln = smgropen(fc_ftag->rnode, InvalidBackendId);
	File		fc_file;
	bool		fc_need_to_close;
	int			fc_result,
				fc_save_errno;

	/* 查看我们是否已经打开文件，或者需要打开它。 */
	if (fc_ftag->segno < fc_reln->md_num_open_segs[fc_ftag->forknum])
	{
		fc_file = fc_reln->md_seg_fds[fc_ftag->forknum][fc_ftag->segno].mdfd_vfd;
		strlcpy(fc_path, FilePathName(fc_file), MAXPGPATH);
		fc_need_to_close = false;
	}
	else
	{
		char	   *fc_p;

		fc_p = fc__mdfd_segpath(fc_reln, fc_ftag->forknum, fc_ftag->segno);
		strlcpy(fc_path, fc_p, MAXPGPATH);
		pfree(fc_p);

		fc_file = PathNameOpenFile(fc_path, O_RDWR | PG_BINARY);
		if (fc_file < 0)
			return -1;
		fc_need_to_close = true;
	}

	/* 同步文件。 */
	fc_result = FileSync(fc_file, WAIT_EVENT_DATA_FILE_SYNC);
	fc_save_errno = errno;

	if (fc_need_to_close)
		FileClose(fc_file);

	errno = fc_save_errno;
	return fc_result;
}

/*
 * 根据文件标签解除链接文件。将路径写入输出
 * 缓冲区，以便调用者可以在错误消息中使用它。
 *
 * 成功时返回 0，失败时返回 -1，并设置 errno。
 */
int mdunlinkfiletag(const FileTag *fc_ftag, char *fc_path)
{
	char	   *fc_p;

	/* 计算路径。 */
	fc_p = relpathperm(fc_ftag->rnode, MAIN_FORKNUM);
	strlcpy(fc_path, fc_p, MAXPGPATH);
	pfree(fc_p);

	/* 尝试解除链接文件。 */
	return unlink(fc_path);
}

/*
 * 检查给定候选请求在处理 SYNC_FILTER_REQUEST 请求时是否与给定标签匹配。
 * 这将被调用以查找所有待处理的请求，以决定是否忘记它们。
 */
bool mdfiletagmatches(const FileTag *fc_ftag, const FileTag *fc_candidate)
{
	/*
	 * 目前我们仅将过滤请求用作在删除数据库时丢弃所有调度
	 * 回调的方式。我们将针对所有与 SYNC_FILTER_REQUEST 请求中的
	 * ftag 具有相同数据库 OID 的候选者返回 true，因此它们会被忘记。
	 */
	return fc_ftag->rnode.dbNode == fc_candidate->rnode.dbNode;
}
